#ifndef SHNET_H
#define SHNET_H
/**
 * @author romix.lab
 * @date August, 2013
 */
#include <QObject>
#include <QUdpSocket>
#include <QHostAddress>
#include <QVariant>
#include <QStringList>
#include "url.h"
#include "sh_global.h"

// can't be done in function because of QDataStream
/// @todo better solution without copy paste ?
#define sh_net_startmsg() QByteArray datagram; \
    QDataStream out(&datagram, QIODevice::WriteOnly); \
    out.setVersion(QDataStream::Qt_5_0); \
    out << (quint8)1; /*version*/ \
    out << m_nodeName;
#define sh_net_sendmsg() m_udp->writeDatagram(datagram.data(), \
    datagram.size(), m_groupAddress, m_port);

class PropertyMapper;
class PropertyFilter;
class DynamicQObject;
class SignalFilter;

/**
 * @brief The ShNet class
 * This class gives access to Sh network
 * <smth> smth with length 1 byte
 * [smth] smth with dynamic size
 * Frame: <protocol version (now 1)>[sender node name]<frame type>[data till the end]
 */
class SHSHARED_EXPORT ShNet : public QObject
{
    Q_OBJECT
public:
    explicit ShNet(QObject *parent = 0);
    ~ShNet();

    /**
     * @brief join try to join network with given multicast address
     * @param multicastGroup group to join
     * @param port any available port
     * @param nodeName unique name of this node
     * @param nodeGroups groups to which this node belongs
     * @param nodeInfo additional info about this node, i.e. description, or author
     * @return
     */
    bool join(const QHostAddress &interface,
              const QHostAddress &multicastGroup, quint16 port,
              const QString &nodeName, const QStringList &nodeGroups = QStringList(),
              const QVariantMap &nodeInfo = QVariantMap());
    /**
     * @brief setProperty send PROPERTY_SET frame with given url
     * @param propertyUrl
     */
    void setProperty(const Url &propertyUrl);
    /**
     * @brief setProperty set local property and send PROPERTY_CHANGE frame if value is new
     * @param netName
     * @param value
     */
    void setProperty(const QString &netName, const QVariant &value);
    /**
     * @brief getProperty get local property
     * @param name
     * @return
     */
    QVariant getProperty(const QString &name) const;
    /**
     * @brief addProperty
     * @param netName
     * @param propertyType
     * @param netGroups
     * @param object if propertyType is OBJECT or QML)
     * @param objectPropertyName
     * @param info additional info about this node, i.e. description, or author
     */
    void addProperty(const QString &netName, bool isQuickProperty,
                     QObject *object, const char *objectPropertyName,
                     const QStringList &netGroups = QStringList(),
                     const QVariantMap &info = QVariantMap());
    /**
     * @brief bindProperty
     * @param bindTo
     * @param object
     * @param propertyName
     * connects object->propertyNameChanged(type) to setProperty(bindTo)
     * creates propertyFilter(bindTo) and connects it to
     *  object->setProperty(propertyName, val)
     */
    void bindProperty(const Url &bindTo, QObject *object, const char *propertyName,
                      bool isQuickProperty);
    /**
     * @brief propertyFilter returns PropertyFilter instance
     * @return
     * Example usage:
     * net->propertyFilter()->add("node/property1", PropertyFilter::SLOT_CALL,
     *                               object, SLOT(slotName(QVariant));
     */
    PropertyFilter *propertyFilter() const;
    /**
     * @brief addSignal broadcast signal it when source->sourceSignalName signal catched
     * @param name signal name in this network
     * @param source QObject with sourceSignalName signal
     * @param sourceSignalName
     * example addSignal("signal_net_name", object, SIGNAL(signal1(QVariant))
     */
    void addSignal(const QString &name,
                   QObject *source, const char *sourceSignalName);
    /**
     * @brief signalFilter returns SignalFilter instance
     * @return
     * Example: net->signalFilter()->connect("pc1/signal2", object, SLOT(s(QVariant)));
     */
    SignalFilter *signalFilter() const;
    /**
     * @brief addFunction function must be QVariant f(QVariant);
     * @param name
     * @param object QMetaObject::invokeMethod will be called with this object
     * @param method must be QVariant f(QVariant)
     * @param functionGroups function can be called by this groups, without a name
     */
    void addFunction(const QString &name,
                     QObject *object,
                     const char *method,
                     const QStringList &functionGroups = QStringList());
    void execFunction(const Url &url);

private slots:
    void recievePendingDatagrams(); // get one datagram and call processDatagram()

private:
    //
    // networking
    //
    void processDatagram(QByteArray &datagram);
    QUdpSocket *m_udp;
    quint16 m_port;
    QHostAddress m_groupAddress;
    enum class FRAME_TYPE {FUNCTION_CALL,
                     FUNCTION_CALL_RESULT,
                     PROPERTY_SET,
                     PROPERY_CHANGE_NOTIFY,
                     NETWORK_SIGNAL};

    //
    // info
    //
    QString m_nodeName;
    QStringList m_nodeGroups;
    QVariantMap m_nodeInfo;

    //
    // properties
    //
    Q_INVOKABLE void property_changed(const QString &netName, const QVariant &value);
    void set_property(const UrlNode &propertyNode, const QVariant &value);
    typedef struct {
        QStringList groups;
        QVariantMap info;
    } property_t;
    QHash<QString, property_t> m_properties;
    PropertyMapper *m_propertyMapper;
    // notifies
    PropertyFilter *m_propertyFilter;
    // binding
    QHash<QString, Url> m_bindedProperties;
    PropertyMapper *m_bindedMapper;
    Q_INVOKABLE void binded_property_changed(const QString &name,
                                             const QVariant &value);

    //
    // signals & slots
    //
    DynamicQObject *m_dynamicQObject;
    Q_INVOKABLE void signal_catched(const QString &name,
                                    const QVariantList &parameters);
    SignalFilter *m_signalFilter;

    //
    // functions
    //
    typedef struct {
        QObject *object;
        const char *method;
        QStringList groups;
    } function_t;
    QHash<QString, function_t> m_functions;
    void function_call(const UrlNode &node, const QVariant &arguments, const QString &senderNodeName);
    void function_exec_result(const QString &functionName,
                              const QString &senderNodeName, const QVariant &retVal);
};

#endif // SHNET_H
